#include "video.h"
#include "shell.h"
#include "defs.h"
#include "keyboard.h"
#include "uart.h"

/* Variables Globales */

static void chatShell (void);
void move_cursor(int screen_pos);

extern int screen_pos;
extern char format;
int commNum= 0;
int upcount= 0;
static historyT hist;

/* Puerto COM a usar */
int com_port;

extern bufferT buff;
extern int d_cant_rows;

/* Posicion del "cursor remoto" */
extern int r_screen_pos;
extern int prompt_pos;
extern int d_frow;

extern int serialc;
extern int serialo;

void
Shell()
{
	int c;
	int aux, num = 0;
	static char line[COMM_LEN+1];
	hist.first = 0;
	hist.last = 0;
	hist.current = 0;
	static char test[COMM_LEN+1];
	
	setLights();
	format= RED_TXT;
	printWelcome();
	format= WHITE_TXT;
	printPrompt();
	
	while(1)
	{
		c = GetChar(&buff);
		SetCursor(screen_pos);
		
		if((num < COMM_LEN || c == '\n' || c == '\b' || c == UPKEY ||
			c == DOWNKEY) && c != EOF)
		{
			switch(c)
			{
				case NPC:	break;
				case ESC:	break;
				case '\n':	line[num] = '\0';
							/* si presiono ENTER chequeo que sea un comando
							 * valido */
							ProcessComand(line);

							/* lo copio al buffer del history */
							cpyBuff(line, &hist);
							num = 0;
							putChar(c);
							upcount = 0;
							printPrompt();
							break;
							
				case '\b':	if(num > 0)
						  	{
								line[--num] = ' ';	
								putChar(c);
							}
							break;
							
				case '\t':	if(num < COMM_LEN - 4)
							{
								putChar(c);
								for(aux= 0; aux<4; aux++)
									line[num+aux]= ' ';
								num+=4;
							}
							break;

				case UPKEY:	line[num] = '\0';
							/* si presiono la felcha me muevo en history */
							if((aux = UpHistory(line, &hist)))
								num = aux;
							break;

				case DOWNKEY:	DownHistory(line, &hist);
							/* si presiono la felcha me muevo en history */
								if( hist.current == hist.last )
									num= 0;
								break;

				default:	putChar(c);
							line[num++] = c;
							break;
			}
		}
		if(num >= COMM_LEN && c != '\n' && c != '\b' && c != UPKEY &&
			c != DOWNKEY && c != EOF)
			/* si llegue al final de la linea valida para comandos BEEP */
			_Beep();	
	}	
	return;
}

int
UpHistory(char * line, historyT * hist)
{
	int num = 0, len, i;
	
	if(!HistoryEmpty(hist) && hist->current != hist->first )
	{
		if(!upcount)
			cpyBuff(line, hist);
			
		hist->current = (hist->current - 1) % MAX_COMM;
		/* borro lo que habia en la linea */
		BlankLine(screen_pos/(80*VID_CHAR));
		screen_pos -= (screen_pos%(80*VID_CHAR));
		printPrompt();
		/* muestro lo que habia guardado en history */
		myprint(hist->history[hist->current], 0);
		len = strLen(hist->history[hist->current]);
		num = strCpy(line, hist->history[hist->current], len);

		upcount++;
	}
	
	return num;
}

int
DownHistory(char * line, historyT * hist)
{
	int num = 0, len;

	if(!HistoryEmpty(hist) && hist->current != hist->last)
	{
		hist->current = (hist->current + 1) % MAX_COMM;
		/* borro lo que habia en la linea */
		BlankLine(screen_pos/(80*VID_CHAR));
		screen_pos -= screen_pos%(80*VID_CHAR);
		printPrompt();
		/* muestro lo que habia guardado en history */
		myprint(hist->history[hist->current], 0);
		len = strLen(hist->history[hist->current]);
		num = strCpy(line, hist->history[hist->current], len);
	}
}

int
strLen(char * string)
{
	int i;
	
	for(i = 0; *((char *)string+i) != '\0' ; i++)
		;
	return i;
}

int
strCpy(char * string1, char * string2, int len)
{
	int i;
	
	for (i = 0; i < len; i++)
		*((char*)string1+i) = *((char*)string2+i);
	
	return i;
}

int
strCmp(char * s, char * t)
{
	while(*s++ == *t++)
		if(*s == '\0' && *t== '\0')
			return 0;
	return 1;
}
void
ProcessComand(char * line)
{
	static char chat[] = "chat";
	static char help[] = "help";
	static char clear[] = "clear";
	static char reboot[] = "reboot";
	static char notValidComm[] = "Error: not a valid command. Try 'help'.";
	
	if( !strCmp(line, chat) )
	{
		/* si ingreso chat ingreso al chat shell */
		chatShell ();
		screen_pos= 0;
	}
	else if( !strCmp(line, help) )
		printHelp();
	else if( !strCmp(line, reboot) )
		reBoot();
	else if( !strCmp(line, clear) )
		cls();
	else
		myprint(notValidComm, 1);
	
	return;
}

/* Me devuelve en line lo que ingreso el usuario inclusive \0 */
void
getline (char * line)
{
	int c, cont = 1, num=0, aux;
	
	while (cont)
	{
		c = GetChar(&buff);
		SetCursor(screen_pos);
		if((num < COMM_LEN || c == '\n' || c == '\b') && c != EOF)
		{
			switch(c)
			{
				case '\n':	line[num] = '\0';
							cont = 0;
							break;
				case '\b': 	if(num > 0)
							{
								line[--num] = ' ';	
								putChar(c);
							}
							break;
				case '\t': 	if(num < COMM_LEN - 4)
							{
								putChar(c);
								for(aux= 0; aux<4; aux++)
									line[num+aux]= ' ';
								num+=4;
							}					
							break;
				default:    if( IsLetter(c) || IsNumber(c) )
							{
								putChar(c);
								line[num++] = c;
							}
			}
		}
	}
	return ;
}

static int
validBaud (char * option, int * ans)
{
	*ans = atoi (option);
	/* valores validos de baud */
	if (*ans != 100 && *ans != 300 && *ans != 1200 && 
		*ans != 2400 && *ans != 4800 && *ans != 9600 && *ans != 19200 &&
		*ans != 38400 && *ans != 57600 && *ans != 115200)
		return 0;
	return 1;
}

static int
validBits (char * option)
{
	int ans = atoi (option);
    
    switch (ans)
    {
        case 5: return WLENGHT_5;
        case 6: return WLENGHT_6;
        case 7: return WLENGHT_7;
        case 8: return WLENGHT_8;
	    default: return -1;
    }
}

static int
validParidad (char * option)
{
	/* valores validos de paridad */
	static char no[] = "no";
	static char odd[] = "odd";
	static char even[] = "even";
	static char mark[] = "mark";
	static char space[] = "space";
	if (!strCmp (option, no))
		return PARITY_NONE;
	if (!strCmp (option, odd))
		return PARITY_ODD;
	if (!strCmp (option, even))
		return PARITY_EVEN;
	if (!strCmp (option, mark))
		return PARITY_MARK;
	if (!strCmp (option, space))
		return PARITY_SPACE;
	return -1;
}

static int
validStop (char * option)
{
	/* valores validos de bit de parada */
	int ans = atoi (option);
	if (ans == 1)
        return STOP_BIT_ONE; 
    if (ans == 2)
		return STOP_BIT_TWO;
	return -1;
}

static int
validPort (char * option)
{
    static char com1[] = "COM1";
    static char com2[] = "COM2";
    static char com3[] = "com1";
    static char com4[] = "com2";

    if (!strCmp (option, com1) || !strCmp (option, com3))
        return COM1;
    if (!strCmp (option, com2) || !strCmp (option, com4))
        return COM2;
    return 0;
}

static void
setup (void)
{
  static char line3[COMM_LEN];
  static char com[] = "Choose COM port (COM1, COM2):  ";
  static char baud[] = "Pick baud(100, 300, 1200, 2400, 4800, 9600,"
		  " 19200, 38400, 57600, 115200):";
  static char bits[] = "Insert word lenght (5, 6, 7, 8): ";
  static char paridad[] = "Insert parity option (no, odd, even, mark, space): ";
  static char stop_bit[] = "Insert amount of stop bits (1, 2): ";
	
	int ibaud, ibits, iparity, istop_bit;
	
	BlankScreen ();
    screen_pos = 0;
    
    /* Pregunto al usuario hasta que ingrese un valor valido */
    do{
        myprint (com, 1);
        getline (line3);
    } while (!(com_port = validPort (line3)));
	
	do{
		myprint (baud, 1);
		getline (line3);
	} while (!validBaud (line3, &ibaud));
	
	do{
		myprint (bits, 1);
		getline (line3);
	} while ((ibits = validBits (line3)) == -1);
	
	do{
		myprint (paridad, 1);
		getline (line3);
	} while ((iparity = validParidad (line3)) == -1);
	
	do{
		myprint (stop_bit, 1);
		getline(line3); 
	} while ((istop_bit = validStop (line3)) == -1);

	/* configuro UART con los valores ingresados */
	setupUART (com_port, ibaud, (unsigned char) ibits, (unsigned char) iparity,
				 (unsigned char) istop_bit);
	
	return ;
}

static void
chatShell (void)
{
	int c;
	int aux, num = 0, auxf,  r;
	static char divisor[] = "-----------------------"
				" CHAT (Presione 'ESC' para salir) -----------------------";

	setup ();
	
	BlankScreen ();
	/* no utilizo prompt en el chat shell */
	prompt_pos = -1;
	
	screen_pos = CANT_COLS * CHAT_LINE * VID_CHAR;
	format= RED_TXT;
	myprint (divisor, 1);
	format= WHITE_TXT;
	screen_pos = 0;

	/* los datos recibidos por UART se escriben en 
	 * "la ventana de abajo" */
	r_screen_pos = (CHAT_LINE+2) * (CANT_COLS * VID_CHAR);
	
	/* Habilito IRQ3 y IRQ4 para poder recibir/enviar datos */
    mascaraPIC1(0xFE & 0xFD & 0xF7 & 0xEF);
    
	while (1)
	{	
		/* si hay un caracter en RBR */
		if(serialo)
		{
			/* No quiero ser interrumpido */
			_Cli();
			aux = screen_pos;
			screen_pos = r_screen_pos;
			auxf = d_frow;
		    /* a partir de que linea hago scroll */
			d_frow = CHAT_LINE + 2;
			
			putChar (serialc);
		    
			/* si salgo reseteo el valor de r_screen_pos */
			if (serialc == ESC)
				r_screen_pos = (CHAT_LINE+2) * (CANT_COLS * VID_CHAR);
			else r_screen_pos = screen_pos;
			screen_pos = aux;
			d_frow = auxf;
			serialo = 0;
			_Sti();
		}
		
		c = GetChar(&buff);
		SetCursor(screen_pos);

		if( c != EOF)
		{
            auxf = d_cant_rows;
			d_cant_rows = CHAT_LINE;
			if( c != NPC && c != UPKEY && c != DOWNKEY )
			{
				/* No permito que borre una vez que estoy en la pos 0 */
				if ((screen_pos == 0) && (c == '\b'))
					;
				else
				{	
					/* no quiero ser interrumpido */
					_Cli();
					putChar(c);
					_Sti();
					/* escribo al UART */
					writeToUART (com_port, c);
				}
			}
			/* Si me voy del chat recupero los backup y desenmascaro 
			 * IRQ3 y IRQ4 del PIC */
			if( c == ESC )
			{
				d_cant_rows = auxf;
				BlankScreen ();
				mascaraPIC1(0xFE & 0xFD);
				return;
			}
			d_cant_rows = auxf;
		}
	}	
    mascaraPIC1(0xFE & 0xFD); /* Habilito solo IRQ0 y IRQ1 */	
	return;
}

void
cpyBuff(char * string, historyT * hist)
{
	int len;
	
	if(HistoryFull(hist))
		hist->first = (hist->first + 1) % MAX_COMM;

	if((len = strLen(string)) < 1)
		return;
	
	strCpy(hist->history[hist->last], string, len);
	hist->current = hist->last;
	hist->last = (hist->last + 1) % MAX_COMM;
	hist->current++;
	
	return;
}

int
HistoryFull(historyT * hist)
{
	if(hist->first == (hist->last+1) % MAX_COMM)
		return 1;
		
	return 0;
}

int
HistoryEmpty(historyT * hist)
{
	if(hist->first == hist->last)
		return 1;
		
	return 0;
}

int 
atoi(char * s)
{
	int i, n, sign;

	for (i = 0; s[i] == ' '; i++)
		;
	sign = (s[i] == '-') ? -1 : 1;
	if (s[i] == '+' || s[i] == '-')
		i++;
	for (n = 0; isdigit(s[i]); i++)
		n = 10 * n + (s[i] - '0');

	return sign * n;
}


// Codigo de http://www.osdev.org/wiki/Reboot
/* keyboard interface IO port: data and control
   READ:   status port
   WRITE:  control register */
#define KBRD_INTRFC 0x64

/* keyboard interface bits */
/* keyboard data is in buffer (output buffer is empty) (bit 0) */
#define KBRD_BIT_KDATA 0
/* user data is in buffer (command buffer is empty) (bit 1) */
#define KBRD_BIT_UDATA 1

#define KBRD_IO 0x60 /* keyboard IO port */
#define KBRD_RESET 0xFE /* reset CPU command */

#define bit(n) (1<<(n)) /* Set bit n to 1 */

/* Check if bit n in flags is set */
#define check_flag(flags, n) ((flags) & bit(n))

void reBoot(void)
{
    byte temp;

    asm volatile ("CLI"); /* disable all interrupts */

    /* Clear all keyboard buffers (output and command buffers) */
    do
    {
        temp = _inb(KBRD_INTRFC); /* empty user data */
        if (check_flag(temp, KBRD_BIT_KDATA) != 0)
            _inb(KBRD_IO); /* empty keyboard data */
    } while (check_flag(temp, KBRD_BIT_UDATA) != 0);

	/* pulse CPU reset line */
	asm volatile ("outb %1, %0" : : "dN" ((u16int)KBRD_INTRFC), 
					"a" ((u8int)KBRD_RESET));
    asm volatile ("HLT"); /* if that didn't work, halt the CPU */
}
